package club.justwill.single.impl;

import club.justwill.single.Tools;
import org.apache.commons.codec.binary.Base64;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.sql.*;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ToolsImpl implements Tools {
	public static final String EMPTY_STRING = "";
	public static final int ZERO = 0;
	public static final int DEFAULT_START_INDEX = ZERO;
	public static final int DEFAULT_MAX_RESULTS = 20;
	public static final int UNLIMITED_RESULTS = -1;
	public static final Date DATE_OPEN_START;
	public static final Date DATE_OPEN_END;
	public static final int DURATION_ONE_DAY_MS = 60 * 60 * 1000 * 24;
	public static final int DURATION_ONE_WEEK_MS = DURATION_ONE_DAY_MS * 7;
	private static final Pattern PATTERN_TAGS = Pattern.compile("\\<.*?\\>", Pattern.CASE_INSENSITIVE);
	private static final Pattern CHINESE_MOBILE_PATTERN = Pattern.compile("1[3-8]\\d{9}");
	private static final char[] digits = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
	private static final char[] smallAlphabets = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'};
	private static final char[] bigAlphabets = {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
	private static final int DEFAULT_SALT_SIZE = 32;
	private static final int HASH_ITERATIONS = 1000;
	private static final int HASH_KEY_LENGTH = 192; // bits
	public static TimeZone TIMEZONE_GMT = TimeZone.getTimeZone("GMT");
	public static final String PASSWORD_PATTERN = "^[0-9_a-zA-Z]{6,20}$";
	private static Pattern[] patterns = new Pattern[]{
			// Script fragments
			Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
			// src='...'
			//Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			//Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// lonely script tags
			Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
			Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// get rid of all frame/iframe tags
			Pattern.compile("<i?frame>(.*?)</i?frame>", Pattern.CASE_INSENSITIVE),
			// lonely script tags
			Pattern.compile("</i?frame>", Pattern.CASE_INSENSITIVE),
			Pattern.compile("<i?frame(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// eval(...)
			Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// expression(...)
			Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
			// javascript:...
			Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
			// vbscript:...
			Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
			// onload(...)=...
			Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL)
	};

	static {
		Calendar cal = Calendar.getInstance(TIMEZONE_GMT);
		cal.set(1, 1, 1);
		DATE_OPEN_START = cal.getTime();
		cal.set(9999, 12, 31);
		DATE_OPEN_END = cal.getTime();
	}

	private SimpleDateFormat timeStandard;


	public static String safeTrim(String str) {
		if (str == null) {
			return EMPTY_STRING;
		}
		return str.trim();
	}

	public static boolean isEmpty(String str) {
		return str == null || str.trim().isEmpty();
	}

	public static boolean isEmpty(@SuppressWarnings("rawtypes") Collection c) {
		return c == null || c.isEmpty();
	}

	public static boolean isEmpty(@SuppressWarnings("rawtypes") Map m) {
		return m == null || m.isEmpty();
	}

	public static boolean isEmpty(Object[] c) {
		return c == null || c.length < 1;
	}

	public static boolean isEmpty(byte[] c) {
		return c == null || c.length < 1;
	}

	public static boolean isEmpty(long[] c) {
		return c == null || c.length < 1;
	}

	public static long safeSet(Long val) {
		if (val == null) {
			return ZERO;
		}
		return val;
	}

	public static String upper(String str) {
		if (isEmpty(str)) {
			return EMPTY_STRING;
		}
		return str.toUpperCase();
	}

	public static int count(@SuppressWarnings("rawtypes") Collection c) {
		return c == null ? ZERO : c.size();
	}

	public static Date getDate(Date date, int yearChange, int monthChange, int dayChange, int minutesChange, int secondsChange) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		if (yearChange != ZERO) {
			calendar.add(Calendar.YEAR, yearChange);
		}
		if (monthChange != ZERO) {
			calendar.add(Calendar.MONTH, monthChange);
		}
		if (dayChange != ZERO) {
			calendar.add(Calendar.DAY_OF_YEAR, dayChange);
		}
		if (minutesChange != ZERO) {
			calendar.add(Calendar.MINUTE, minutesChange);
		}
		if (secondsChange != ZERO) {
			calendar.add(Calendar.SECOND, secondsChange);
		}
		return calendar.getTime();
	}

	public static boolean isValidEmail(String email) {
		return !isEmpty(email) && email.matches("^([\\w\\!\\#$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\`{\\|\\}\\~]+\\.)*[\\w\\!\\#$\\%\\&\\'\\*\\+\\-\\/\\=\\?\\^\\`{\\|\\}\\~]+@(((((\\w{1}[\\w\\-]{0,62}\\w{1})|[a-zA-Z])\\.)+[a-zA-Z]{2,6})|(\\d{1,3}\\.){3}\\d{1,3}(\\:\\d{1,5})?)$");
	}

	public static boolean isValidMobile(String number) {
		if (isEmpty(number)) {
			return false;
		} else {
			Matcher matcher = CHINESE_MOBILE_PATTERN.matcher(number);
			return matcher.matches();
		}
	}

	public static String readClassPathResourceAsString(String path) {
		StringBuffer data = new StringBuffer();
		BufferedReader reader = new BufferedReader(new InputStreamReader(ToolsImpl.class.getClassLoader().getResourceAsStream(path)));
		String line;
		try {
			while ((line = reader.readLine()) != null) {
				data.append(line).append(System.lineSeparator());
			}
			reader.close();
		} catch (Exception e) {
			e.printStackTrace();
			return EMPTY_STRING;
		}
		return data.toString().trim();
	}

	public static String escapeHtmlScripts(String value, Pattern srcPattern) {
		if (value != null) {
			// NOTE: It's highly recommended to use the ESAPI library and uncomment
			// the following line to
			// avoid encoded attacks.
			// value = ESAPI.encoder().canonicalize(value);

			// Avoid null characters
			value = value.replaceAll("\0", "");

			// Remove all sections that match a pattern
			for (Pattern scriptPattern : patterns) {
				value = scriptPattern.matcher(value).replaceAll("");
			}

			/* strip off absolute path to relative path
			Matcher absolutePathMatcher =  srcPattern.matcher(value);
			if (absolutePathMatcher.find()) {
				value = absolutePathMatcher.replaceAll("$1");
			}*/
		}
		return safeTrim(value);
	}

	public static String removeHtmlTags(String content) {
		return safeTrim(PATTERN_TAGS.matcher(content).replaceAll(" "));
	}

	public static Integer parseInteger(String number) {
		try {
			return new Integer(number);
		} catch (Exception e) {
			return null;
		}
	}

	public static Float parseFloat(String number) {
		try {
			return new Float(number);
		} catch (Exception e) {
			return null;
		}
	}

	public static Long parseLong(String number) {
		try {
			return new Long(number);
		} catch (Exception e) {
			return null;
		}
	}

	public static int parseInt(String number) {
		Integer num = parseInteger(number);
		if (num == null) {
			return ZERO;
		}
		return num;
	}

	public static String md5(String text) {
		try {
			MessageDigest md = MessageDigest.getInstance("MD5");
			md.reset();
			md.update(text.getBytes());
			String hash = new BigInteger(1, md.digest()).toString(16);
			//Now we need to zero pad it if you actually want the full 32 chars.
			while (hash.length() < 32) {
				hash = "0" + hash;
			}
			return hash;
		} catch (Exception e) {
			return EMPTY_STRING;
		}
	}

	private static String sign(byte[] data, String method, int digits) {
		try {
			MessageDigest md = MessageDigest.getInstance(method);
			md.reset();
			md.update(data);
			String hash = new BigInteger(1, md.digest()).toString(16);
			//Now we need to zero pad it if you actually want the full 64 chars.
			while (hash.length() < digits) {
				hash = "0" + hash;
			}
			return hash;
		} catch (Exception e) {
			return EMPTY_STRING;
		}
	}

	public static String sha256(byte[] data) {
		return sign(data, "SHA-256", 64);
	}

	public static String md5(byte[] data) {
		return sign(data, "MD5", 64);
	}

	public static String generateRandomPassword(int length) {
		StringBuffer pwd = new StringBuffer();
		Random ran = new Random();
		while (pwd.length() < length) {
			switch (ran.nextInt(3)) {
				case 0:
					pwd.append(digits[ran.nextInt(digits.length)]);
					break;
				case 1:
					pwd.append(smallAlphabets[ran.nextInt(smallAlphabets.length)]);
					break;
				case 2:
					pwd.append(bigAlphabets[ran.nextInt(bigAlphabets.length)]);
					break;
			}
		}
		return pwd.toString();
	}

	private static String generateSalt() {
		Random r = new SecureRandom();
		byte[] saltBinary = new byte[DEFAULT_SALT_SIZE];
		r.nextBytes(saltBinary);
		return Base64.encodeBase64String(saltBinary);
	}

	private static String hashPasswordAddingSalt(String password, byte[] salt) {
		if (isEmpty(password)) {
			return EMPTY_STRING;
		}
		try {
			SecretKeyFactory f = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
			SecretKey key = f.generateSecret(new PBEKeySpec(
							password.toCharArray(), salt, HASH_ITERATIONS, HASH_KEY_LENGTH)
			);
			return Base64.encodeBase64String(key.getEncoded());
		} catch (Exception e) {
			return EMPTY_STRING;
		}
	}

	public static String hashPasswordAddingSalt(String password) {
		byte[] salt = generateSalt().getBytes();
		return Base64.encodeBase64String(salt) + '$' + hashPasswordAddingSalt(password, salt);
	}

	public static boolean isValidPassword(String password, String hashedPassword) {
		String[] saltAndPass = hashedPassword.split("\\$");
		if (saltAndPass.length != 2) {
			throw new IllegalStateException(
					"The stored password have the form 'salt$hash'");
		}
		String hashOfInput = hashPasswordAddingSalt(password, Base64.decodeBase64(saltAndPass[0]));
		return hashOfInput.equals(saltAndPass[1]);
	}


	@Override
	public String serializeTime(Date time) {
		timeStandard = new SimpleDateFormat("yyyy-MM-dd");
		if (time == null) {
			return null;
		}
		return timeStandard.format(time);
	}

	@Override
	public Date deserializeTime(String timeString) {
		timeStandard = new SimpleDateFormat("yyyy-MM-dd");
		if (timeString == null || timeString.isEmpty()) {
			return null;
		}
		try {
			return timeStandard.parse(timeString);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static String serializeDate(java.util.Date date) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		return sdf.format(date);
	}

	public static String serializeDateMMSS(java.util.Date date) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(date);
	}

	public static Date deserializeDate(String date) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		try {
			return sdf.parse(date);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return null;
	}

	public static String serializeTimeStamp(Timestamp time) {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(time);
	}

	public static Timestamp deserializeTimeStamp(String timeStamp) {
		Date date = str2Date(timeStamp.trim(), "yyyy-MM-dd");
		return new Timestamp(date.getTime());
	}

	public static Date str2Date(String str, String format){
		if (null != str && !"".equals(str)) {
			SimpleDateFormat sdf = new SimpleDateFormat(format);
			Date date = null;
			try {
				date = sdf.parse(str);
				return date;
			} catch (ParseException e) {
				return null;
			}
		}
		return null;
	}



	@Override
	public String escapeXss(String content, boolean textOnly) {
		if (content == null) {
			return ToolsImpl.EMPTY_STRING;
		}
		if (textOnly) {
			//return Tools.safeTrim(HtmlUtils.htmlEscape(content));
			return safeTrim(content);
		} else {
			return escapeHtmlScripts(content, null);
		}
	}

	@Override
	public boolean isNumber(String value) {
		return isInteger(value) || isDouble(value);
	}

	@Override
	public boolean isOverLength(String value, int length) {
		if(value == null) {
			return false;
		} else {
			return value.length() > length;
		}

	}

	@Override
	public boolean isBelowLength(String value, int length) {
		if(value == null) {
			return false;
		} else {
			return value.length() < length;
		}

	}

	/**
	 * 判断字符串是否是整数
	 */
	public static boolean isInteger(String value) {
		try {
			Integer.parseInt(value);
			return true;
		} catch (NumberFormatException e) {
			return false;
		}
	}

	/**
	 * 判断字符串是否是浮点数
	 */
	public static boolean isDouble(String value) {
		try {
			Double.parseDouble(value);
			if (value.contains("."))
				return true;
			return false;
		} catch (NumberFormatException e) {
			return false;
		}
	}

	public static boolean isOverDecimal(Double costing, int begin, int end) {
		return true;
	}

	public static boolean isQualifiedPassword(String password){
		Pattern pattern = Pattern.compile(PASSWORD_PATTERN);
		Matcher matcher = pattern.matcher(password);
		return matcher.matches();
	}

	public static String getRandomString(int length) { //length表示生成字符串的长度
		String base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
		Random random = new Random();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < length; i++) {
			int number = random.nextInt(base.length());
			sb.append(base.charAt(number));
		}
		return sb.toString();
	}

	public static Date changeLongToDate(long timeStamp){
		SimpleDateFormat df = new SimpleDateFormat(" yyyy-MM-dd HH:mm:ss ");
		String dateString = df.format(timeStamp);
		Date date = null;
		try {
			date = df.parse(dateString);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return date;
	}

}